/**
 * 
 */
package be.gwi.alfresco.run.facade.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.InvalidTypeException;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.rating.Rating;
import org.alfresco.service.cmr.rating.RatingService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;

import be.gwi.alfresco.run.exception.FacadeException;
import be.gwi.alfresco.run.facade.RunFacade;
import be.gwi.alfresco.run.model.Like;
import be.gwi.alfresco.run.model.Run;
import be.gwi.alfresco.run.model.RunModel;
import be.gwi.alfresco.run.model.RunType;
import be.gwi.alfresco.run.model.Runner;

/**
 * 
 * @author gabriel.wilems@gmail.com
 * @see RunFacade
 * 
 * @version 1.0
 * @since 1.0
 */
public class RunFacadeImpl extends AbstractFacadeImpl implements RunFacade {
    private static final int  YEAR_OFFSET = 1900;

    private static Log        logger      = LogFactory.getLog(RunFacadeImpl.class);

    private FileFolderService fileFolderService;
    private RatingService     ratingService;

    /**
     * Initializes the facade.
     * 
     * Verifies that all the attributes have been set.
     * 
     * @throws IllegalArgumentException
     *             if not all attributes have been set.
     */
    public void init() {
        super.init();
        Assert.notNull(this.fileFolderService, "The filefolder service must have been set");
        Assert.notNull(this.ratingService, "The rating service must have been set");
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.facade.RunFacade#addRunToRunnerHistory(be.gwi.alfresco
     * .run.model.Run, be.gwi.alfresco.run.model.Runner)
     */
    @Override
    public void addRunToRunnerHistory(Run run, Runner runner) throws FacadeException {
        try {
            NodeRef yearNodeRef = getYearNodeRef(run, runner);
            // NodeRef runNodeRef = fileFolderService.create(yearNodeRef,
            // getRunName(run), RunModel.TYPE_RUN).getNodeRef();
            NodeRef runNodeRef = nodeService.createNode(yearNodeRef, ContentModel.ASSOC_CONTAINS,
                    ContentModel.ASSOC_CONTAINS, RunModel.TYPE_RUN).getChildRef();
            nodeService.setProperty(runNodeRef, ContentModel.PROP_NAME, getRunName(run));
            updateRun(runNodeRef, run);
        } catch (FileExistsException e) {
            throw new FacadeException("Unable to add run to runner history - Name file exists", e);
        } catch (InvalidNodeRefException e) {
            throw new FacadeException("Unable to add run to runner history - NodeRef not valid", e);
        } catch (InvalidTypeException e) {
            throw new FacadeException("Unable to add run to runner history -Type not valid", e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.facade.RunFacade#getHistory(be.gwi.alfresco.run.model
     * .Runner, java.lang.String)
     */
    @Override
    public List<Run> getHistory(Runner runner, String year) throws FacadeException {
        List<Run> history = new ArrayList<Run>();
        NodeRef yearNodeRef = nodeService.getChildByName(runner.getHistoryNodeRef(), ContentModel.ASSOC_CONTAINS, year);
        List<ChildAssociationRef> yearChildren = nodeService.getChildAssocs(yearNodeRef);
        // List<FileInfo> yearChildren = fileFolderService.list(yearNodeRef);
        for (int i = 0; i < yearChildren.size(); i++) {
            NodeRef runNodeRef = yearChildren.get(i).getChildRef();
            history.add(getRun(runNodeRef));
        }

        return history;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.facade.RunFacade#getHistoryByRunType(be.gwi.alfresco
     * .run.model.Runner, be.gwi.alfresco.run.model.RunType)
     */
    @Override
    public List<Run> getHistoryByRunType(Runner runner, RunType runType) throws FacadeException {
        List<Run> history = new ArrayList<Run>();
        NodeRef historyNodeRef = runner.getHistoryNodeRef();
        if (historyNodeRef != null) {
            List<ChildAssociationRef> yearChildAssoc = nodeService.getChildAssocs(historyNodeRef);
            for (int i = 0; i < yearChildAssoc.size(); i++) {
                NodeRef yearNodeRef = yearChildAssoc.get(i).getChildRef();
                List<ChildAssociationRef> runChildAssoc;
                if (RunType.ALL.equals(runType)) {
                    runChildAssoc = nodeService.getChildAssocs(yearNodeRef);
                } else {
                    runChildAssoc = nodeService.getChildAssocsByPropertyValue(yearNodeRef, RunModel.PROP_RUN_TYPE,
                            runType);
                }
                for (int j = 0; j < runChildAssoc.size(); j++) {
                    NodeRef runNodeRef = runChildAssoc.get(j).getChildRef();
                    history.add(getRun(runNodeRef));
                }
            }
        }

        return history;
    }

    /**
     * Update Run from given nodeRef
     * 
     * @param runnerNodeRef
     * @param runner
     * @throws InvalidNodeRefException
     */
    private void updateRun(NodeRef runNodeRef, Run run) throws InvalidNodeRefException {
        this.nodeService.setProperty(runNodeRef, RunModel.PROP_RUN_DATE, run.getRunDate());
        this.nodeService.setProperty(runNodeRef, RunModel.PROP_RUN_TYPE, run.getRunType().toString());
        this.nodeService.setProperty(runNodeRef, RunModel.PROP_RUN_DISTANCE, run.getRunDistance());
        this.nodeService.setProperty(runNodeRef, RunModel.PROP_RUN_URL, run.getRunUrl());
    }

    /**
     * Get Year node Ref (runner -> history folder -> year folder -> run type)
     * 
     * @param run
     * @param runner
     * @return
     * @throws FileExistsException
     */
    private NodeRef getYearNodeRef(Run run, Runner runner) throws FileExistsException {
        NodeRef yearNodeRef;
        @SuppressWarnings("deprecation")
        int year = YEAR_OFFSET + run.getRunDate().getYear();
        NodeRef historyNodeRef = runner.getHistoryNodeRef();
        String yearString = String.valueOf(year);

        yearNodeRef = nodeService.getChildByName(historyNodeRef, ContentModel.ASSOC_CONTAINS, yearString);
        if (yearNodeRef == null) {
            if (logger.isDebugEnabled()) {
                logger.debug("History year node " + yearString + " not found => create it");
            }
            yearNodeRef = fileFolderService.create(historyNodeRef, yearString, ContentModel.TYPE_FOLDER).getNodeRef();

        }
        return yearNodeRef;
    }

    /**
     * Returns a run with all its informations.
     * 
     * @param runNodeRef
     *            the run nodeRef.
     * 
     * @return a run with all its informations.
     */
    private Run getRun(NodeRef runNodeRef) throws FacadeException {
        Run run = new Run();
        Map<QName, Serializable> props = null;
        try {
            props = this.nodeService.getProperties(runNodeRef);
        } catch (Exception e) {
            String message = new StringBuilder("An error occured while getting run ").append(runNodeRef).toString();
            throw new FacadeException(message, e);
        }
        run.setNodeRef(runNodeRef);
        run.setRunDate((Date) props.get(RunModel.PROP_RUN_DATE));
        run.setRunDistance((Double) props.get(RunModel.PROP_RUN_DISTANCE));
        run.setRunType(RunType.valueOf((String) props.get(RunModel.PROP_RUN_TYPE)));
        run.setRunUrl((String) props.get(RunModel.PROP_RUN_URL));
        run.setLike(getLikeFromNodeRef(runNodeRef));
        return run;
    }

    /**
     * Retrieve likeCount and isLike from given nodeRef
     * 
     * @param runNodeRef
     * @return
     */
    private Like getLikeFromNodeRef(NodeRef runNodeRef) {
        int likeCount = ratingService.getRatingsCount(runNodeRef, "likesRatingScheme");
        Rating rating = ratingService.getRatingByCurrentUser(runNodeRef, "likesRatingScheme");
        boolean isLiked = (rating != null) && (rating.getScore() > 0);
        Like like = new Like();
        like.setLikeCount(likeCount);
        like.setLiked(isLiked);
        return like;
    }

    /**
     * Get Run name
     * 
     * TODO move it to helper class
     * 
     * @param run
     * @return
     */
    @SuppressWarnings("deprecation")
    private String getRunName(Run run) {
        Date runDate = run.getRunDate();
        StringBuilder runName = new StringBuilder();
        runName.append(runDate.getYear());
        runName.append(runDate.getMonth());
        runName.append(runDate.getDate());
        runName.append(runDate.getHours());
        runName.append(runDate.getMinutes());
        runName.append(runDate.getSeconds());
        runName.append(GUID.generate());
        return runName.toString();
    }

    /**
     * @param fileFolderService
     *            the fileFolderService to set
     */
    public void setFileFolderService(FileFolderService fileFolderService) {
        this.fileFolderService = fileFolderService;
    }

    /**
     * @param ratingService
     *            the ratingService to set
     */
    public void setRatingService(RatingService ratingService) {
        this.ratingService = ratingService;
    }

}
